package com.generator.service;


import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.common.aspect.ValueRuleAspect;
import com.generator.common.GeneratorParam;
import com.generator.entity.TemplateConfig;
import com.generator.entity.ValueRule;
import com.generator.gen.*;
import com.generator.util.FormatUtil;
import com.generator.util.FreemarkerUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;

/**
 * 生成代码逻辑
 */
@Service
public class GeneratorService {

    static ExecutorService executorService = Executors.newFixedThreadPool(2);

    @Autowired
    private TemplateConfigService templateConfigService;

    @Value("${gen.format-xml:false}")
    private String formatXml;
    @Value("${valueRule.file.outdir:D:\\MyProgram\\JavaProject\\fm-admin\\rules\\value-rule}")
    private String valueRuleoutDir;
    @Value("${valueRule.file.dir:D:\\MyProgram\\JavaProject\\fm-admin\\fm-generator\\src\\main\\resources\\ftl\\rule}")
    private String valueRuledir;
    @Autowired
    private ValueRuleService valueRuleService;
    @Autowired
    private ValueRuleAspect valueRuleAspect;


    /**
     * 生成代码内容,map的
     *
     * @param generatorParam 生成参数
     * @param generatorConfig 数据源配置
     * @return 一张表对应多个模板
     */
    public List<CodeFile> generate(GeneratorParam generatorParam, GeneratorConfig generatorConfig) {
        List<SQLContext> contextList = this.buildSQLContextList(generatorParam, generatorConfig);
        List<CodeFile> codeFileList = new ArrayList<>();
        FileOutputStream fos =null;
        /**
         * 生成代码文件
         */
        for (SQLContext sqlContext : contextList) {
            setPackageName(sqlContext, generatorParam.getPackageName());
            setDelPrefix(sqlContext, generatorParam.getDelPrefix());
            setAuthor(sqlContext, generatorParam.getAuthor());
            setModule(sqlContext,generatorParam.getModule());
            String module="";
            //文件目录可以使用变量
            if(StringUtils.hasLength(generatorParam.getModule())){
                module=File.separator+generatorParam.getModule();
            }
            for (int tcId : generatorParam.getTemplateConfigIdList()) {
                TemplateConfig template = templateConfigService.getById(tcId);
                String folder = template.getFolder();
                if (StringUtils.isEmpty(folder)) {
                    folder = template.getName()+module;
                }else{
                    folder = doGenerator(sqlContext, folder);
                }
                setFolder(sqlContext, folder);
                //获取文件名
                String fileName = doGenerator(sqlContext, template.getFileName());
                String content = doGenerator(fileName,sqlContext, template.getContent());
                content = this.formatCode(fileName, content);
                if(folder!=null) {
                    try {
                        File dir=new File(folder);
                        if(!dir.exists()){
                            dir.mkdirs();
                        }
                        fos = new FileOutputStream(folder + File.separator + fileName);
                        fos.write(content.getBytes());
                    } catch (FileNotFoundException e) {
                        throw new RuntimeException(e);
                    } catch (IOException e) {
                        throw new RuntimeException(e);
                    } finally {
                        if (fos != null) {
                            try {
                                fos.close();
                            } catch (IOException e) {
                                throw new RuntimeException(e);
                            }
                        }
                    }
                }
                CodeFile codeFile = new CodeFile();
                codeFile.setFolder(folder);
                codeFile.setFileName(fileName);
                codeFile.setContent(content);
                codeFileList.add(codeFile);
            }
        }

        /**
         * 生成规则文件
         */
        List<ValueRule> valueRules = valueRuleService.getBaseMapper().selectList(
                new LambdaQueryWrapper<ValueRule>().select().eq(ValueRule::getDatabsourceId, generatorParam.getDatasourceConfigId()));
        if(!CollectionUtils.isEmpty(valueRules)){
            Map<String, List<ValueRule>> valueRuleMap = valueRules.stream().collect(Collectors.groupingBy(ValueRule::getTableName));
            for (String tablename:valueRuleMap.keySet()) {
                List<ValueRule> valueRuleslist = valueRuleMap.get(tablename);
                List<JSONObject>jsonObjects=new ArrayList<>();
                for (int i = 0,len=valueRuleslist.size(); i <len ; i++) {
                    ValueRule valueRule = valueRuleslist.get(i);
                    String content = valueRule.getContent();
                    if(StringUtils.hasLength(content)){
                        JSONObject jsonObject = JSON.parseObject(content);
                        JSONArray nodes = jsonObject.getJSONArray("nodes");
                        Map<String,JSONObject>nodesmap=new HashMap<>();
                        for (int j = 0,len1=nodes.size(); j <len1 ; j++) {
                              nodesmap.put(nodes.getJSONObject(j).getString("id"),nodes.getJSONObject(j));
                        }
                        JSONArray edges = jsonObject.getJSONArray("edges");
                        for (int j = 0,len1=nodes.size(); j <len1 ; j++) {
                            buildchildrenNode(nodes.getJSONObject(j),nodesmap,edges);
                        }
                        JSONObject rootNode = findRootNode(nodesmap, edges);
                        rootNode.put("name",valueRule.getColumn());
                        jsonObjects.add(rootNode);
                    }

                }

                String ftlName = String.format("%sValueRule.yml", tablename);
                FreemarkerUtil.initConfig(valueRuledir, "TableValueRule.ftl");
                FreemarkerUtil.generatorRule(valueRuleoutDir+File.separator+ftlName,new HashMap(){
                    {
                        put("tableName",tablename);
                        put("valurRules",jsonObjects);
                    }
                });
            }

        }
        return codeFileList;
    }


    public JSONObject findRootNode( Map<String,JSONObject> nodesmap, JSONArray edges){
        JSONObject root=null;
        Set<String> keys = nodesmap.keySet();
        for (String key:keys) {
            JSONObject jsonObject = nodesmap.get(key);
            if(!"circle".equals(jsonObject.getString("type"))){
                continue ;
            }
            boolean flat=true;
            for (int i = 0,len=edges.size(); i <len ; i++) {
                   if(key.equals(edges.getJSONObject(i).getString("targetNodeId"))){
                       flat=false;
                       break ;
                   }
            }
            if(flat){
                root=nodesmap.getOrDefault(key,new JSONObject());
                break ;
            }
        }
        return root;
    }
    public void buildchildrenNode(JSONObject node, Map<String,JSONObject> nodesmap, JSONArray edges){
        for (int i = 0,len=edges.size(); i <len ; i++) {
            if(node.getString("id").equals(edges.getJSONObject(i).getString("sourceNodeId"))){
                ((List) node.computeIfAbsent("childrens",(key)-> new ArrayList<>()))
                        .add(nodesmap.get(edges.getJSONObject(i).getString("targetNodeId")));
            }
        }
    }

    // 格式化代码
    private String formatCode(String fileName, String content) {
        if (Objects.equals("true", formatXml) && fileName.endsWith(".xml")) {
            return FormatUtil.formatXml(content);
        }
        return content;
    }


    /**
     * 返回SQL上下文列表
     *
     * @param generatorParam 参数
     * @param generatorConfig 配置
     * @return 返回SQL上下文
     */
    private List<SQLContext> buildSQLContextList(GeneratorParam generatorParam, GeneratorConfig generatorConfig) {

        List<String> tableNames = generatorParam.getTableNames();
        List<SQLContext> contextList = new ArrayList<>();
        SQLService service = SQLServiceFactory.build(generatorConfig);

        TableSelector tableSelector = service.getTableSelector(generatorConfig);
        tableSelector.setSchTableNames(tableNames);

        List<TableDefinition> tableDefinitions = tableSelector.getTableDefinitions();

        for (TableDefinition tableDefinition : tableDefinitions) {
            SQLContext sqlContext = new SQLContext(tableDefinition);
            sqlContext.setDbName(generatorConfig.getDbName());
            contextList.add(sqlContext);
        }

        return contextList;
    }

    private void setPackageName(SQLContext sqlContext, String packageName) {
        if (StringUtils.hasText(packageName)) {
            sqlContext.setPackageName(packageName);
        }
    }

    private void setFolder(SQLContext sqlContext, String folder) {
        if (StringUtils.hasText(folder)) {
            sqlContext.setPackageSubPath(folder);
        }
    }
    private void setModule(SQLContext sqlContext, String module) {
        if (StringUtils.hasText(module)) {
            sqlContext.setModule(module);
        }
    }

    private void setDelPrefix(SQLContext sqlContext, String delPrefix) {
        if (StringUtils.hasText(delPrefix)) {
            sqlContext.setDelPrefix(delPrefix);
        }
    }

    private void setAuthor(SQLContext sqlContext, String author) {
        if (StringUtils.hasText(author)) {
            sqlContext.setAuthor(author);
        }
    }

    private String doGenerator(SQLContext sqlContext, String template) {
        if (template == null) {
            return "";
        }
        Map context = new HashMap<>();
        Object pkColumn = sqlContext.getTableDefinition().getPkColumn();
        if (pkColumn == null) {
            pkColumn = Collections.emptyMap();
        }
        context.put("context", sqlContext);
        context.put("module",sqlContext.getModule());
        context.put("table", sqlContext.getTableDefinition());
        context.put("pk", pkColumn);
        context.put("columns", sqlContext.getTableDefinition().getColumnDefinitions());
        context.put("csharpColumns", sqlContext.getTableDefinition().getCsharpColumnDefinitions());

        return FreemarkerUtil.generator( template,context);
    }
    private String doGenerator(String fileName,SQLContext sqlContext, String template) {
        if (template == null) {
            return "";
        }
        Map context = new HashMap<>();
        Object pkColumn = sqlContext.getTableDefinition().getPkColumn();
        if (pkColumn == null) {
            pkColumn = Collections.emptyMap();
        }
        context.put("context", sqlContext);
        context.put("table", sqlContext.getTableDefinition());
        context.put("pk", pkColumn);
        context.put("module",sqlContext.getModule());
        context.put("columns", sqlContext.getTableDefinition().getColumnDefinitions());
        context.put("csharpColumns", sqlContext.getTableDefinition().getCsharpColumnDefinitions());
        return FreemarkerUtil.generator(fileName, template,context);
    }

}
